package com.nulldev.util.web.flexibleKit.scfk.protocols.http1;

import java.io.InputStream;
import java.util.Map;

import com.nulldev.util.io.IOUtils;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.web.HttpClient.contentGen.GeneratableContent;
import com.nulldev.util.web.flexibleKit.scfk.HTTPRequestType;
import com.nulldev.util.web.flexibleKit.scfk.SCFKRequest;
import com.nulldev.util.web.flexibleKit.scfk.SCFKResponse;
import com.nulldev.util.web.flexibleKit.scfk.io.streams.Stream;
import com.nulldev.util.web.flexibleKit.scfk.protocols.PreferredProtocol;
import com.nulldev.util.web.flexibleKit.scfk.protocols.ProtocolRequest;

public class Http1Request implements ProtocolRequest {

	/*
	 * TODO: - Add HTTP/0.9 support (do not send headers and body on request, do not
	 * expect headers on response) - Add HTTP/1.0 support (do not send the Host
	 * header) (DONE)
	 */

	public static final Http1Request INSTANCE = new Http1Request();

	@Override
	public String getProtocolName() {
		return "HTTP";
	}

	@Override
	public String getProtocolVersion() {
		return "1.1";
	}

	@Override
	public double implementationVersion() {
		return 0.04;
	}

	private static final Logger log = LoggerFactory.getLogger();

	@Override
	public SCFKResponse onRequestWrite(final SCFKRequest r, final Stream s) throws Exception {
		while (!s.awaitConnection()) {
			try {
				Thread.sleep(1L);
			} catch (InterruptedException ex) {
			}
		}
		if (s.isClosed())
			throw new RuntimeException("Cannot use closed stream!");
		final String path = r.getURL().getRawPath() + (r.getURL().getQuery() != null ? "?" + r.getURL().getQuery() : "");
		s.printAll(r.getMethod().raw + " " + path + " " + r.getProtocol().protString);
		log.debug(r.getMethod().raw + " " + path + " " + r.getProtocol().protString + " (" + r.getURL() + ")");
		if (r.getProtocol() == PreferredProtocol.HTTP_1_1) {
			s.printAll("Host: " + r.getURL().getHost());
			log.debug("Host: " + r.getURL().getHost());
			if (s.shouldKeepAlive()) {
				s.printAll("Connection: Keep-Alive");
			} else {
				s.printAll("Connection: close");
			}
//			final KeepAliveParams pka = ProtocolKeepAlive.getParamsFor(r.getProtocol());
//			if (pka != null && pka != ProtocolKeepAlive.UNSUPPORTED) {
//				s.printAll("Keep-Alive: timeout=" + pka.maximumDuration + ", max=" + pka.maximumReuse);
//			} TODO: Check if this needs to be done.
		}
		if (r.getProtocol().protVersion >= 1.0) {
			for (final Map.Entry<String, String> e : r.headers().entrySet()) {
				if (e.getKey() == null || e.getKey().isEmpty() || e.getValue() == null || e.getValue().isEmpty())
					continue;
				s.printAll(e.getKey() + ": " + e.getValue());
				log.debug(e.getKey() + ": " + e.getValue());
			}
		}
		if (r.getMethod() != HTTPRequestType.GET && r.body() != null && r.getProtocol().protVersion >= 1.1) {
			if (r.body() instanceof GeneratableContent) {
				s.printAll("Content-Type: " + ((GeneratableContent) r.body()).getContentType());
			} else
				s.printAll("Content-Type: application/x-www-form-urlencoded");
			if (r.body() instanceof String) {
				s.printAll("Content-Length: " + ((String) r.body()).length());
			} else if (r.body() instanceof GeneratableContent) {
				s.printAll("Content-Length: " + ((GeneratableContent) r.body()).getContentLength());
			} else if (r.body() instanceof byte[]) {
				s.printAll("Content-Length: " + ((byte[]) r.body()).length);
			} else if (r.body() instanceof InputStream) {
				final InputStream is = ((InputStream) r.body());
				if (is.markSupported()) { // FIXME
					int length = 0;
					final byte[] b = new byte[IOUtils.MEMORY_ALLOC_BUFFER];
					int l;
					while ((l = (is.read(b))) > 0) {
						length += l;
					}
					is.reset();
					s.printAll("Content-Length: " + length);
				} else {
					s.printAll("Content-Length: " + is.available());
				}
			} else {
				throw new RuntimeException("Canoot send data using: " + r.body());
			}
			s.invokeLR();
			if (r.body() instanceof String) {
				s.writeAll((String) r.body());
			} else if (r.body() instanceof GeneratableContent) {
				s.writeAll(((GeneratableContent) r.body()).makeAndClear());
			} else if (r.body() instanceof byte[]) {
				s.writeAll((byte[]) r.body());
			} else if (r.body() instanceof InputStream) {
				final InputStream is = ((InputStream) r.body());
				final byte[] buffer = new byte[IOUtils.MEMORY_ALLOC_BUFFER];
				int n = 0;
				while (-1 != (n = is.read(buffer))) {
					s.write(buffer, 0, n);
				}
			} else {
				throw new RuntimeException("Cannot send data using: " + r.body());
			}
			s.invokeLR();
		} else
			s.invokeLR();
//		JVM.println("WRITE DONE");
		return Http1Response.INSTANCE.onResponseRead(r, s);
	}

}
